package org.batatasframework.support.definition.impl;

import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.batatasframework.expression.CommandExpression;
import org.batatasframework.expression.CommandExpressionArgument;
import org.batatasframework.expression.CommandExpressionExtra;
import org.batatasframework.expression.ExpressionClause;
import org.batatasframework.support.database.FieldFormatSetter;
import org.batatasframework.support.database.JoinType;
import org.batatasframework.support.database.metadatas.TableMetadata;
import org.batatasframework.support.definition.JoinDefinition;
import org.batatasframework.support.definition.TableDefinition;
import org.batatasframework.support.definition.TableDefinitionBuilder;
import org.batatasframework.support.definition.TableDefinitionParser;
import org.batatasframework.support.definition.impl.TableDefinitionImpl.DefaultFieldDefinition;
import org.batatasframework.support.format.BooleanFormat;
import org.batatasframework.support.format.OptionalFormat;
import org.batatasframework.support.loader.DatabasePropertiesParser;
import org.batatasframework.support.type.JavaType;
import org.batatasframework.support.type.OptionalType;
import org.batatasframework.support.type.OptionalType.OptionalItem;
import org.batatasframework.support.type.OptionalTypeMultiple;
import org.batatasframework.support.type.OptionalTypeSingle;
import org.batatasframework.values.ValueGetter;

import cn.bonoon.kernel.util.StringHelper;

/**
 * 解析定义文件的情况
 * @author jackson
 *
 */
public abstract class AbstractTableDefinitionParser implements TableDefinitionParser{

	protected TableMetadata buildTableMetadata(DatabasePropertiesParser parserContext) throws Exception{
		return parserContext.getTableMetadata();
	}
	
	@Override
	public TableDefinition parse(DatabasePropertiesParser parserContext) throws Exception {

		TableMetadata tableMetadata = buildTableMetadata(parserContext);
		
		int fieldOrdinal = parserContext.getInteger("ordinal.start.point",0);
		int fieldStep = parserContext.getInteger("ordinal.step",0);
		fieldStep = Math.max(1, fieldStep);
		
		try(TableDefinitionBuilder searchableContextBuilder = parserContext.getTableFactoryRegistry().buildSearchableContextBuilder(tableMetadata)){
			
		List<ExpressionClause> joins = parserContext.getGroup("[joins]").cloneOf();
			
			Map<String, JoinDefinition> joinMapped = new HashMap<>();
			
			if(!joins.isEmpty()){
				Map<String, ExpressionClause> joinSES = new HashMap<>();
				ExpressionClause tables = null;
				for(ExpressionClause se : joins){
					if(se.getName().equals("tables")){
						tables = se;
					}else{
						joinSES.put(se.getName().toString(), se);
					}
				}
				 
				 if(null != tables){
					 String[] jts = tables.getString().split(",");
					 for(String jt : jts){
						 ExpressionClause se = joinSES.get(jt);
						 
						 /*
						  * 没有定义关联表的详细信息，是否能用默认的值；
						  * 如果不能使用默认的值，则这里必须抛出异常
						  * 如：关联字段，主表和关联表是否都能使用默认的值？
						  */
						 if(null == se) continue;
						 
						 /*
						  * -a ：表示使用别名，如果有定义了别名，则字段的定义可以直接使用别名
						  * -f ：用于关联表关联到主表的字段
						  * -m :用于关联关联表的主表的字段
						  * -s ：schema
						  * -c ：catalog
						  */
						 CommandExpression command = new CommandExpression(se, "-a", "-f", "-m", "-s", "-c", "-must");
						 JoinType joinType = command.toEnum(JoinType.class, JoinType.LEFT);
						 String joinAlias = command.getString("-a");
						 String joinSchema = command.getString("-s");
						 String joinCatalog = command.getString("-c");
						 String joinField = command.getString("-f");
						 String masterField = command.getString("-m");
						 boolean must = command.getBoolean("-must");
						 
						 joinAlias = StringHelper.get(joinAlias, se.getExtra().toString(), se.getName().toString());
						 masterField = StringHelper.get(masterField, "C_ID");
						 
						 JoinDefinition jd = searchableContextBuilder.join(joinSchema, joinCatalog, se.getName().toString(), must);
						 jd.on(joinType, masterField, joinField);
						 
						 //支持关联表使用别名和表名两种方式进行定义
						 joinMapped.put(se.getName().toString(), jd);
						 joinMapped.put(joinAlias, jd);
					 }
				 }
			}
			
			List<ExpressionClause> fields = parserContext.getGroup("[fields]").mergeOf();
			
			for(ExpressionClause se : fields){
				
				if(se.getName().with("$concat")){
					
					/*
					 * 第0位是中文名称，后面的是字段，包括了使用关联表
					 */
					_parseField(searchableContextBuilder.concat(se.getString()), se.getExtra(), joinMapped);
					
				}else if(se.getName().with("$format")){
					
					/*
					 * 第0位是中文名称，后面的是字段，包括了使用关联表
					 * 第1位是格式化字符串
					 */
					String value = se.getValue().getString();
					int fb = value.indexOf(' ');
					if(fb < 0) continue;
					String title = value.substring(0, fb);
					String format = value.substring(fb + 1);
					
					_parseField(searchableContextBuilder.format(title, format), se.getExtra(), joinMapped);
					
				} else {
	
					/*
					 * 这里得先判断这个字段是否可用，如果是定义成其它格式的，忽略这个字段的处理
					 */
					String[] names = se.getName().getNames();
					
					String fieldName = names[0];
					JoinDefinition joinDefinition = null;
					if(names.length == 1){
						String joinTableName = fieldName;
						joinDefinition = joinMapped.get(joinTableName);
						if(null == joinDefinition){
							
							/*
							 * 这种情况下应该是：field.options等情况，所以这个不需要处理
							 */
							continue;
						}
						fieldName = names[1];
						
					}else if(names.length > 1){
						
						/*
						 * 出现2个"."以上的，则表示是字段的某些类型的定义，如：table.field.options
						 */
						continue;
					}
					
					/*
					 * 这里为什么要把字段的关联表放在字段名的定义里呢，因为如果是两个表关联的话，
					 * 两个表是有可能有同名的字段的，如果不在字段名上加以定义，则会出现重复的字段定义；
					 * 目前的版本是不允许有重复的字段定义的
					 * 
					 * -t ：titile
					 * -o ：排序
					 * 
					 */
					CommandExpressionExtra command = new CommandExpressionExtra(se, 
							new String[]{"-t", "-n", "-u", "-o", "-f", "-max", "-min"},
							".options", ".options.default");
					
					DefaultFieldDefinition fieldDefinition = searchableContextBuilder.field(joinDefinition, fieldName);
					fieldDefinition.setTitle(command.getString("-t"));
					
					int ordinal = command.getInteger("-o",0);
					if(ordinal == 0){
						fieldOrdinal += fieldStep;
						ordinal = fieldOrdinal;
					}
					fieldDefinition.setOrdinal(ordinal);
					
					parseOptional(fieldDefinition, command);
				}
			}
			
			return searchableContextBuilder.build();
		}
	}
	
	protected void parseOptional(FieldFormatSetter fieldFormatSetter, CommandExpressionExtra command) throws Exception{
		JavaType type = command.toEnum(JavaType.class);
		CommandExpressionArgument cvOptions = command.getValue("options");
		if(!cvOptions.disabled()){
			OptionalType optionalType;
			long value = 1;
			
			if(null == type){
				/*
				 * 默认是下拉选项；
				 * 也可以定义成其他的选择。
				 */
				type = JavaType.SELECT;

				optionalType = new OptionalTypeSingle();

			}else if(type == JavaType.CHECKBOX || type == JavaType.MULTISELECT){
				
				/*
				 * 这里是定义了多选的情况
				 */
				optionalType = new OptionalTypeMultiple();
				value = 0;

			}else if(type == JavaType.RADIO || type == JavaType.SELECT){
				
				/*
				 * 多选的情况
				 */
				optionalType = new OptionalTypeSingle();

			}else{
				
				/*
				 * 如果有定义了选项，但使用的是其它的类型，如：String，Date等，那么应该把类型调整回来；
				 * 另一种方式是忽略掉选项，使用回原来的操作？
				 * 两种情况未确定，目前使用第一种！
				 */
				type = JavaType.SELECT;
				
				optionalType = new OptionalTypeSingle();
			}

			//还要再处理一下
			OptionalItem last = null;
			for(String to : cvOptions.getString().split("\\s\\|\\s")){
				if(to.startsWith("#")){
					if(null != last){
						last.setSuffix(to.substring(1));
					}
				}else{
					last = optionalType.add(to, value++);
				}
			}

			if(optionalType.isEmpty()) throw new Exception("选项的定义不正确，没有定义任何可选项！");

			boolean nullable = false;
			String nullName = null;
			if(optionalType.size() == 1){
				
				/*
				 * 如果处理完了长度只有1个的情况，则直接使用checkbox的方式
				 * 因为只有一个选项，下拉选择或RADIO方式只有一个选项，不能操作
				 */
				type = JavaType.CHECKBOX;
				if(optionalType instanceof OptionalTypeMultiple){
					optionalType.clear();
					optionalType.add(last.resetValue(0));
				}else{
					optionalType = new OptionalTypeMultiple();
					optionalType.add(last.getName(), 0L);
				}
				
				nullable = true;
				nullName = "否";
			}
			
			String nullOptionName = cvOptions.getDefaultValue().getString();
			if(StringHelper.isNotEmpty(nullOptionName)){
				nullable = true;
				nullName = nullOptionName;
			}
			fieldFormatSetter.setFormat(new OptionalFormat(optionalType, nullName, nullable));

		}else{
			
			/*
			 * 这里在定义文件里的定义如：.2  - 表示有两位小数点，所以是小数类型
			 */
			int decimalDigits = command.getInteger(".", 0);
			if(null == type){
				if(decimalDigits > 0){
					type = JavaType.DECIMAL;
				}else{
					type = JavaType.NUMBER;
				}
			}
			
			if(type == JavaType.DECIMAL && decimalDigits > 0){
				
				/*
				 * 这种是需要使用小数点的格式化处理的情况
				 */
	        	StringBuffer buffer = new StringBuffer(decimalDigits + 2);
	        	buffer.append("0.");
	        	for(int i = 0; i < decimalDigits; i++) buffer.append('0');
	        	fieldFormatSetter.setFormat(new DecimalFormat(buffer.toString()));
	        	
			}
			
			//另外看一下是否定义了格式化的语句
			String formatString = StringHelper.get(command.getString("format"), command.getString("-f"));
			if(type == JavaType.DECIMAL){
				if(!fieldFormatSetter.hasFormate() && StringHelper.isNotEmpty(formatString)){
					
					/*
					 * 这里优先使用.2的方式定义的格式，如果还定义了格式化的字符串的，则会忽略掉格式化字符串
					 */
					fieldFormatSetter.setFormat(new DecimalFormat(formatString));
				}
				
				/*
				 * 如果是数值类型的，这里还要判断是否对数据的大小范围进行的限制
				 */
			}else if(type == JavaType.NUMBER){
				if( StringHelper.isNotEmpty(formatString)){
					fieldFormatSetter.setFormat(new DecimalFormat(formatString));
				}
				
				/*
				 * 是否限制了数据的范围
				 */
			}else if(type == JavaType.BOOLEAN
					|| type == JavaType.CHECKBOX
					|| type == JavaType.MULTISELECT
					|| type == JavaType.RADIO
					|| type == JavaType.SELECT){
				
				/*
				 * 在没有定义选项的情况下，定义了选项的类型的，则全重置为boolean类型；
				 * 否则直接抛出异常？
				 */
				type = JavaType.BOOLEAN;
				fieldFormatSetter.setFormat(new BooleanFormat(formatString));
				
			}
			
			if(type == JavaType.DATE && StringHelper.isNotEmpty(formatString)){
				fieldFormatSetter.setFormat(new SimpleDateFormat(formatString));
			}
			
		}
			
		fieldFormatSetter.setJavaType(type);
	}

	private void _parseField(AbstractMultipleFieldDefinition mField, 
			//String[] fs,
			ValueGetter extraValue,
			Map<String, JoinDefinition> jonMapped) throws Exception {
		//for(int i = 1; i < fs.length; i++){
		for(String fn : extraValue.getString().split(",")){
			//String fn = fs[i];
			String[] sepNames = fn.split(".");
			if(sepNames.length > 1){
				String jointable = sepNames[0];
				String fieldName = sepNames[1];
				
				JoinDefinition joinDefinition = jonMapped.get(jointable);
				if(null == joinDefinition){
					throw new Exception("未定义的关联表：" + jointable);
				}
				
				mField.append(joinDefinition, fieldName);
			}else{
				mField.append(fn);
			}
		}
	}
	
}
